#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the version 3 of the GNU Lesser General Public License
#   as published by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright (c) NEC Deutschland GmbH, NEC HPC Europe

import collectd, socket, time


#Constants
METRIC_SOURCE = "collectd"
DEFAULT_SERVER_PORT = 10000
DEFAULT_SERVER = "localhost"


class SocketTxtWriter:
    '''
    This class is a collectd (4.10.2) plugin. It mainly registers a write() and a log() method.
    These methods are called periodically by the main collectd thread. Write and log receive
    value and log data respectively and send them via a client socket of type INET or UNIX
    to all connected servers. The data format is plain text and can be evaluated as a
    regular Python expression.
    To activate this plugin add something like the following to collectd.conf.

    <Plugin python>
        ModulePath "/usr/lib64/python2.6"
        ModulePath "."
        LogTraces true
        Interactive false
        Import "socket_txt_writer"

        <Module socket_txt_writer>
            <host "localhost">
                path "/var/tmp/collectd"
            </host>
            <host "127.0.0.1">
                port 10000
            </host>
        </Module>
  
    </Plugin>
    LoadPlugin python

    Limitations: hostnames in the config file must be unique; log and writer are not synchonized 
    '''
    
    module = ""
    hosts= {}
    servers = {}

    @staticmethod
    def __init__():
        pass

    @staticmethod
    def _connect( hostname ):
        port_or_path = SocketTxtWriter.hosts[hostname]  
        try:
            if isinstance( port_or_path, int ):
                SocketTxtWriter.servers[hostname] = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
                SocketTxtWriter.servers[hostname].connect( ( hostname, port_or_path ) )
            else:
                SocketTxtWriter.servers[hostname] = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM )
                SocketTxtWriter.servers[hostname].connect( port_or_path )
            print SocketTxtWriter.module, ": connected to", hostname, port_or_path
        except Exception, e:
            print SocketTxtWriter.module, ": can not connect to", hostname, port_or_path, e
            SocketTxtWriter.servers[hostname] = None

    @staticmethod
    def config( configuration ):
        if len( configuration.children ) == 0:
            SocketTxtWriter.hosts[DEFAULT_SERVER] = DEFAULT_SERVER_PORT
        for child in configuration.children:
            hostname = DEFAULT_SERVER
            port_or_path = DEFAULT_SERVER_PORT
            if child.key == "host":
                hostname = child.values[0]
            for hostchild in child.children:
                if hostchild.key == "port":
                    port_or_path = int( hostchild.values[0] )
                elif hostchild.key == "path":
                    assert hostname == "localhost", "UNIX sockets can only be used on localhost"
                    port_or_path = hostchild.values[0]
                SocketTxtWriter.hosts[hostname] = port_or_path
        SocketTxtWriter.module = configuration.values[0]
        for hostname in SocketTxtWriter.hosts.keys():
            SocketTxtWriter._connect( hostname )
            
    @staticmethod
    def log( severity, message ):
        #log() and writer() must be called from the same thread within collectd (!?)
        s = "Metric( name='log', source='%s', host='%s', time=%d, value='%d', output='%s')\n" % \
            ( METRIC_SOURCE, socket.gethostname(), int( time.time() ), severity, str( message ) )
        #print SocketTxtWriter.module, "log :", s
        for hostname, server in SocketTxtWriter.servers.items():
            if server == None:
                SocketTxtWriter._connect( hostname )
            if not server == None:
                try:
                    server.send( s )
                except Exception, e:
                    print SocketTxtWriter.module, ": can not send log to", hostname, e
                    SocketTxtWriter.servers[hostname] = None

    @staticmethod
    def shutdown():
        for hostname, server in SocketTxtWriter.servers.items():
            print SocketTxtWriter.module, ": closing connection to", hostname
            server.close()

    @staticmethod
    def write( val, data=None ):
        #log() and writer() must be called from the same thread within collectd (!?)
        for value in val.values:
            s = "Metric( name='%s', source='%s', host='%s', type='%s', time=%d, value=%f )\n" % \
                            ( val.plugin, METRIC_SOURCE, val.host, val.type, val.time, value )
            #print SocketTxtWriter.module, "value :", s
            for hostname, server in SocketTxtWriter.servers.items():
                if server == None:
                    SocketTxtWriter._connect( hostname )
                if not server == None:
                    try:
                        server.send( s )
                    except Exception, e:
                        print SocketTxtWriter.module, ": can not send value to", hostname, e
                        SocketTxtWriter.servers[hostname] = None

collectd.register_config( SocketTxtWriter.config )
collectd.register_init( SocketTxtWriter.__init__ )
collectd.register_log( SocketTxtWriter.log )
collectd.register_shutdown( SocketTxtWriter.shutdown )
collectd.register_write( SocketTxtWriter.write )
